package com.ccb.sc.login.controller;

import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import com.ccb.sc.github.yydzxz.common.util.ByteDanceAppIdHolder;
import com.ccb.sc.github.yydzxz.miniprogram.api.impl.request.qrcode.CreateQrCodeRequest;
import com.ccb.sc.github.yydzxz.miniprogram.api.impl.response.login.Code2sessionResponse;
import com.ccb.sc.auth.TokenAuth;
import com.ccb.sc.auth.TokenModel;
import com.ccb.sc.auth.TokenUtil;
import com.ccb.sc.constant.AppConst;
import com.ccb.sc.common.service.ISmsService;
import com.ccb.sc.login.vo.ByteDanceLoginVo;
import com.ccb.sc.member.service.AppMemberService;
import com.ccb.sc.common.modules.member.service.UserThirdAuthService;
import com.ccb.sc.member.vo.AppMemberVo;
import com.ccb.sc.common.bind.annotation.CurrentUser;
import com.ccb.sc.common.bind.annotation.Limit;
//psl import com.ccb.sc.common.bind.exception.BizException;
import com.ccb.sc.common.bind.response.ResBody;
import com.ccb.sc.common.code.BizCodeFace;
import com.ccb.sc.common.code.ErrorCode;
import com.ccb.sc.common.constant.CommonRedisKey;
import com.ccb.sc.common.constant.RegexPatterns;
import com.ccb.sc.common.enums.Gender;
import com.ccb.sc.common.modules.member.entity.Member;
import com.ccb.sc.common.modules.member.entity.UserThirdAuth;
import com.ccb.sc.common.modules.member.enums.LoginType;
import com.ccb.sc.common.modules.member.enums.MemberSource;
import com.ccb.sc.common.modules.member.enums.MemberStatus;
import com.ccb.sc.common.modules.member.service.ByteDanceMaService;
import com.ccb.sc.common.util.oss.OssManager;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

/**
 * @author ken
 * @version 1.0
 * @date 2022-08-05 1:19
 */
@Slf4j
@RestController
@RequestMapping("/app/bytedance")
@Api(value = "抖音小程序登录", tags = "抖音小程序登录")
public class ByteDanceMaUserController {

    @Autowired
    private ByteDanceMaService maService;

    @Autowired
    private AppMemberService memberService;

    @Autowired
    private UserThirdAuthService userThirdAuthService;

    @Autowired
    private ISmsService smsService;

    @Autowired
    private OssManager ossManager;

    @Value("${aliyun.oss.bucket:'mifen-vip'}")
    private String bucketsName;

    @PostMapping("/login")
    @ApiOperation(value = "抖音小程序登录", response = AppMemberVo.class)
    public ResBody<?> login(@RequestParam(required = false) String code, @RequestParam(required = false) String anonymousCode) {
        if (!StringUtils.hasText(code) && !StringUtils.hasText(anonymousCode)) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("抖音小程序登录失败，code is null"));
        }
        try {
            Code2sessionResponse sessionResult = maService.getByteDanceMaService().getByteDanceMiniProgramLoginService().code2session(code, anonymousCode);
            ByteDanceLoginVo byteDanceLoginVo = new ByteDanceLoginVo();
            byteDanceLoginVo.setSessionKey(sessionResult.getSessionKey());
            byteDanceLoginVo.setOpenId(sessionResult.getOpenid());
            byteDanceLoginVo.setAnonymousOpenid(sessionResult.getAnonymousOpenid());
            byteDanceLoginVo.setUnionId(sessionResult.getUnionid());
            log.info("byteDanceLoginVo:{}", byteDanceLoginVo);
            return ResBody.success(byteDanceLoginVo);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("抖音小程序登录失败，未知错误"));
        } finally {
            //清理ThreadLocal
            ByteDanceAppIdHolder.remove();
        }
        return ResBody.failure();
    }

    @Transactional(rollbackFor = Exception.class)
    @PostMapping("/info")
    @ApiOperation(value = "获取抖音小程序用户信息")
    public ResBody<?> info(@RequestParam String openId, @RequestParam String unionId, @RequestParam(required = false) String inviteId,
                           @RequestParam String sessionKey, @RequestParam String signature, @RequestParam String rawData,
                           @RequestParam String encryptedData, @RequestParam String iv,
                           @RequestParam(required = false) String phoneSessionKey,
                           @RequestParam(required = false) String phoneEncryptedData,
                           @RequestParam(required = false) String phoneIv,
                           HttpServletResponse response) {
        try {
            // 用户信息校验
            if (!maService.getByteDanceMaService().getByteDanceMiniProgramLoginService().checkUserInfo(sessionKey, rawData, signature)) {
                //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("用户信息校验错误"));
            }

            // 解密用户信息
            WxMaUserInfo userInfo = maService.getByteDanceMaService().getByteDanceMiniProgramLoginService().getUserInfo(sessionKey, encryptedData, iv);
            if (userInfo == null) {
                //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("用户信息校验错误"));
            }
            UserThirdAuth userThirdAuth = this.userThirdAuthService.findByAuthIdAndLoginType(openId, LoginType.BYTE_DANCE_MA);
            Member member;
            // 第三方登录信息不存在，首次注册登录
            if (userThirdAuth == null) {
                WxMaPhoneNumberInfo phoneNoInfo = getByteDancePhone(phoneSessionKey, phoneEncryptedData, phoneIv);
                member = this.memberService.findByPhone(phoneNoInfo.getPurePhoneNumber());
                if (member == null) {
                    member = register(phoneNoInfo.getPurePhoneNumber(), userInfo, inviteId);
                }
                userThirdAuth = new UserThirdAuth();
                userThirdAuth.setMemberId(member.getId());
                userThirdAuth.setOauthId(openId);
                userThirdAuth.setUnionId(unionId);
                userThirdAuth.setLoginType(LoginType.BYTE_DANCE_MA);
                this.userThirdAuthService.save(userThirdAuth);
            }else {
                member = this.memberService.getById(userThirdAuth.getMemberId());
                // 如果有第三方登录信息，但是没有用户信息或者手机号
                if (member == null) {
                    WxMaPhoneNumberInfo phoneNoInfo = getByteDancePhone(phoneSessionKey, phoneEncryptedData, phoneIv);
                    member = register(phoneNoInfo.getPurePhoneNumber(), userInfo, inviteId);

                    userThirdAuth.setMemberId(member.getId());
                    userThirdAuth.setUnionId(unionId);
                    this.userThirdAuthService.updateById(userThirdAuth);
                }else {
                    // 没有手机号的用户需要重新获取手机号登录
                    if (!StringUtils.hasText(member.getPhone())) {
                        WxMaPhoneNumberInfo phoneNoInfo = getByteDancePhone(phoneSessionKey, phoneEncryptedData, phoneIv);
                        member.setPhone(phoneNoInfo.getPurePhoneNumber());
                        member.setCountry(phoneNoInfo.getCountryCode());
                        member.setVersion(member.getVersion());
                        this.memberService.updateById(member);
                    }
                }
            }
            if (member.getStatus() == MemberStatus.DISABLED) {
                //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.ACCOUNT_LOCKED));
            }
            String token = TokenUtil.generateToken(member.getId(), sessionKey);
            response.addHeader(AppConst.TOKEN_HEAD_NAME, token);
            return ResBody.success(token);
        } finally {
            //清理ThreadLocal
            ByteDanceAppIdHolder.remove();
        }
    }


    @Transactional(rollbackFor = Exception.class)
    @PostMapping("/infoByPhone")
    @ApiOperation(value = "获取抖音小程序用户信息-手机号验证码")
    public ResBody<?> infoByPhone(@RequestParam String openId, @RequestParam String unionId,
                                  @RequestParam String sessionKey, @RequestParam String signature, @RequestParam String rawData,
                                  @RequestParam String encryptedData, @RequestParam String iv,
                                  @RequestParam(required = false) String phone, @RequestParam(required = false) String smsCode,
                                  @RequestParam(required = false) String inviteId, HttpServletResponse response) {
        try {
            // 用户信息校验
            if (!maService.getByteDanceMaService().getByteDanceMiniProgramLoginService().checkUserInfo(sessionKey, rawData, signature)) {
                //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("用户信息校验错误"));
            }

            // 解密用户信息
            WxMaUserInfo userInfo = maService.getByteDanceMaService().getByteDanceMiniProgramLoginService().getUserInfo(sessionKey, encryptedData, iv);
            if (userInfo == null) {
                //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("用户信息校验错误"));
            }
            UserThirdAuth userThirdAuth = this.userThirdAuthService.findByAuthIdAndLoginType(openId, LoginType.BYTE_DANCE_MA);
            Member member;
            // 第三方登录信息不存在，首次注册登录
            if (userThirdAuth == null) {
                checkPhoneAndSmsCode(phone, smsCode);

                member = this.memberService.findByPhone(phone);
                if (member == null) {
                    member = register(phone, userInfo, inviteId);
                }
                userThirdAuth = new UserThirdAuth();
                userThirdAuth.setMemberId(member.getId());
                userThirdAuth.setOauthId(openId);
                userThirdAuth.setUnionId(unionId);
                userThirdAuth.setLoginType(LoginType.BYTE_DANCE_MA);
                this.userThirdAuthService.save(userThirdAuth);
            }else {
                member = this.memberService.getById(userThirdAuth.getMemberId());
                // 如果有第三方登录信息，但是没有用户信息或者手机号
                if (member == null) {
                    checkPhoneAndSmsCode(phone, smsCode);

                    member = register(phone, userInfo, inviteId);

                    userThirdAuth.setMemberId(member.getId());
                    userThirdAuth.setUnionId(unionId);
                    this.userThirdAuthService.updateById(userThirdAuth);
                }else {
                    // 没有手机号的用户需要重新获取手机号登录
                    if (!StringUtils.hasText(member.getPhone())) {
                        checkPhoneAndSmsCode(phone, smsCode);
                        member.setPhone(phone);
                        member.setVersion(member.getVersion());
                        this.memberService.updateById(member);
                    }
                }
            }
            if (member.getStatus() == MemberStatus.DISABLED) {
                //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.ACCOUNT_LOCKED));
            }
            String token = TokenUtil.generateToken(member.getId(), sessionKey);
            response.addHeader(AppConst.TOKEN_HEAD_NAME, token);
            return ResBody.success(token);
        } finally {
            //清理ThreadLocal
            ByteDanceAppIdHolder.remove();
        }
    }

    private Member register(String phone, WxMaUserInfo userInfo, String inviteId) {
        Member member = new Member();
        member.setPhone(phone);
        member.setSource(MemberSource.BYTE_DANCE_MA);
        member.setNickname(userInfo.getNickName());
        member.setAvatar(userInfo.getAvatarUrl());
        member.setGender(Gender.getByValue(Integer.valueOf(userInfo.getGender())));
        member.setCountry(userInfo.getCountry());
        member.setProvince(userInfo.getProvince());
        member.setCity(userInfo.getCity());
        return this.memberService.registerByMa(member, inviteId);
    }

    private void checkPhoneAndSmsCode(String phone, String smsCode) {
        if (!StringUtils.hasText(phone) && !StringUtils.hasText(smsCode)) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.PHONE_NO_EXISTIS).message("请授权手机号"));
        }
        Assert.isTrue(phone.matches(RegexPatterns.MOBILE_PHONE_NUMBER_PATTERN), "请输入正确的手机号格式");
        boolean result = this.smsService.checkCode(phone, smsCode, String.format(CommonRedisKey.LOGIN_CODE, phone));
        if (!result) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.AUTH_FAIL).message("验证码错误"));
        }
    }

    private WxMaPhoneNumberInfo getByteDancePhone(String sessionKey, String encryptedData, String ivStr) {
        if (!StringUtils.hasText(sessionKey) && !StringUtils.hasText(encryptedData) && !StringUtils.hasText(ivStr)) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.PHONE_NO_EXISTIS).message("请授权手机号"));
        }
        WxMaPhoneNumberInfo phoneNoInfo = maService.getByteDanceMaService()
                .getByteDanceMiniProgramLoginService()
                .getPhoneNoInfo(sessionKey, encryptedData, ivStr);

        if (null == phoneNoInfo) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("抖音小程序获取手机号失败，未知错误"));
        }
        return phoneNoInfo;
    }


    @Deprecated
    @TokenAuth
    @PostMapping("/phone")
    @ApiOperation(value = "更新用户绑定手机号")
    public ResBody<?> phone(@ApiIgnore @CurrentUser TokenModel token,
                            @RequestParam String sessionKey,
                            @RequestParam String encryptedData,
                            @RequestParam String iv) {
        // 解密
        WxMaPhoneNumberInfo phoneNoInfo = maService.getByteDanceMaService().getByteDanceMiniProgramLoginService().getPhoneNoInfo(sessionKey, encryptedData, iv);
        if (null == phoneNoInfo) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("抖音小程序获取手机号失败，未知错误"));
        }
        Member member = this.memberService.getById(token.getId());
        member.setPhone(phoneNoInfo.getPurePhoneNumber());
        member.setCountry(phoneNoInfo.getCountryCode());
        member.setVersion(member.getVersion());
        this.memberService.updateById(member);

        ByteDanceAppIdHolder.remove();//清理ThreadLocal
        return ResBody.success();
    }

    @Limit(period = 5, count = 5)
    @TokenAuth
    @PostMapping("/getQRCode")
    @ApiOperation(value = "获取二维码图片")
    public ResBody<?> getQRCode(@RequestParam String page, @CurrentUser @ApiIgnore TokenModel token){
        try {
            // 兼容旧逻辑
            page = page.split("\\?")[0];
            page += "?inviteid=" + token.getId() + "&source=ma";
            CreateQrCodeRequest request = new CreateQrCodeRequest();
            request.setPath(page);
            request.setAppname("douyin");
            request.setSetIcon(true);
            File qrCodeFile = this.maService.getByteDanceMaService().getByteDanceMiniProgramQrCodeService().createQrcode(request, "");
            String fileStr = encryptToBase64(qrCodeFile.getPath());
            if (!StringUtils.hasText(fileStr)){
                //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("获取二维码图片错误"));
            }
            String url = ossManager.putObject("qrcode/", bucketsName, fileStr);
            return ResBody.success(url);
        } catch (IOException e) {
            log.error("获取二维码图片错误：{}", e.getMessage(), e);
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("获取邀请二维码图片错误"));
        }
        return ResBody.failure();
    }

    private String encryptToBase64(String filePath) {
        if (filePath == null) {
            return null;
        }
        try {
            byte[] b = Files.readAllBytes(Paths.get(filePath));
            return Base64.getEncoder().encodeToString(b);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
